package controller;

import eccezioni.CampoErratoException;
import entity.Annuncio;
import entity.IndirizzoAbitazione;
import entity.SitoAcquisti;
import entity.UtenteRegistrato;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import utilities.AnnuncioConDistanza;
import utilities.QueryBuilder;
import utilities.Regexp;
import utilities.googleMap.DistanceMatrix;

/**
 * Metodi per svolgere la ricerca di un annuncio
 * @author Ale
 */
@Stateless
public class ControllerRicerca implements ControllerRicercaLocal {

    @EJB
    private ControllerGMapsLocal controllerGMaps;
    @PersistenceContext(unitName = "ShippingShareBisBis-ejbPU")
    private EntityManager em;
    
    /**
     * Resistuisce la lista di annunci aperti presenti per una certa regione
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @param regione la regione per cui si cercano gli annunci
     * @return la lista di annunci aperti presenti per la regione specificata
     */
    @Override
    public List<Annuncio> trovaInRegione(UtenteRegistrato utenteLoggato, String regione) {
        QueryBuilder qb = new QueryBuilder("Annuncio", em);

        qb.addWhereEqClause("indirizzoRitiro.regione", regione);
        qb.addWhereLessClause("dataChiusura", new GregorianCalendar());
        qb.addOrderBy("dataChiusura");

        Query q = qb.getQueryObject();

        List annunci =  q.getResultList();
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        return annunci;
    }
    
    /**
     * Recupera la lista di annunci in scadenza, cioè ancora aperti, ordinati per scadenza
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @return la lista di annunci in scadenza
     */
    @Override
    public List<Annuncio> trovaInScadenza(UtenteRegistrato utenteLoggato) {
        QueryBuilder qb = new QueryBuilder("Annuncio", em);
        qb.addWhereLessClause("dataChiusura", new GregorianCalendar());
        qb.addOrderBy("dataChiusura");

        Query q = qb.getQueryObject();
        List annunci =  q.getResultList();
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        return annunci;
    }

    /**
     * Esegue una ricerca tra gli annunci coi parametri specificati, che sono tutti opzionali
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @param url sito a cui si riferisce la ricerca
     * @param vicinoA indirizzo in base a cui sono ordinati i risultati
     * @return la lista di annunci corrispondenti
     * @throws CampoErratoException se ci sono dei parametri di ricerca non nel formato corretto
     */
    @Override
    public List<AnnuncioConDistanza> cerca(UtenteRegistrato utenteLoggato, String url, String vicinoA) throws CampoErratoException {
        
        String errori = controllaCampiRicerca(url, null, null, vicinoA, null, null);
        if (!"".equals(errori)) throw new CampoErratoException(errori);
        
        QueryBuilder qb = new QueryBuilder("Annuncio", em);

        SitoAcquisti sito = null;
        if (url != null && !url.equals("")) {
            sito = new SitoAcquisti();
            sito.setUrl(url);
        }

        if (sito != null) {
            qb.addWhereEqClause("sito.url", sito.getUrl());
        }
        
        // Non cerco tra gli annunci chiusi.
        qb.addWhereLessClause("dataChiusura", new GregorianCalendar());

        Query q = qb.getQueryObject();
        List annunci = q.getResultList();
        
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        if ("".equals(vicinoA) && utenteLoggato != null) vicinoA = utenteLoggato.getIndirizzo().getDescrizione();
        return  utilities.AnnuncioConDistanza.ordina(annunci, vicinoA);

    }

    /**
     * Esegue una ricerca tra gli annunci aperti coi parametri specificati, che sono tutti opzionali
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @param sito sito a cui si riferisce la ricerca
     * @param vicinoA indirizzo in base a cui sono ordinati i risultati
     * @return la lista di annunci aperti corrispondenti
     */
    @Override
    public List<AnnuncioConDistanza> cercaAperti(UtenteRegistrato utenteLoggato, String sito, String vicinoA) {
        
        QueryBuilder qb = new QueryBuilder("Annuncio", em);

        if (Regexp.hasContent(sito)) {
            qb.addWhereEqClause("sito.url", sito);
        }
        qb.addWhereLessClause("dataChiusura", new GregorianCalendar());
 
        Query q = qb.getQueryObject();
        List annunci = q.getResultList();
        
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        if ("".equals(vicinoA) && utenteLoggato != null) vicinoA = utenteLoggato.getIndirizzo().getDescrizione();
        return  utilities.AnnuncioConDistanza.ordina(annunci, vicinoA);
    }

    /**
     * Esegue una ricerca tra gli annunci coi parametri specificati, che sono tutti opzionali
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @param sito sito a cui si riferisce la ricerca
     * @param dataChiusura data massima di chiusura degli annunci cercati
     * @param dataOrdine data massima di ordine degli annunci cercati
     * @param vicinoA indirizzo in base a cui sono ordinati i risultati
     * @param chiusi true sse si vogliono includere anche gli annunci chiusi
     * @return la lista di annunci corrispondenti
     * @throws CampoErratoException se ci sono dei parametri di ricerca non nel formato corretto
     */
    @Override
    public List<AnnuncioConDistanza> cerca(UtenteRegistrato utenteLoggato, String sito, String dataChiusura, String dataOrdine, String vicinoA, boolean chiusi) throws CampoErratoException{
        
        String errori = controllaCampiRicerca(sito, dataChiusura, dataOrdine, vicinoA, null, null);
        if (!"".equals(errori)) throw new CampoErratoException(errori);
        
        QueryBuilder qb = new QueryBuilder("Annuncio", em);

        if (Regexp.hasContent(sito)) {
            qb.addWhereEqClause("sito.url", sito);
        }
        if (Regexp.hasContent(dataChiusura)) {
            qb.addWhereMoreEqClause("dataChiusura", stringToDate(dataChiusura));
        }
        if (Regexp.hasContent(dataOrdine)) {
            qb.addWhereMoreEqClause("dataOrdine", stringToDate(dataOrdine));
        }
        if (!chiusi) {
            qb.addWhereLessClause("dataChiusura", new GregorianCalendar());
        }
        qb.addOrderBy("dataChiusura");


        Query q = qb.getQueryObject();
        List annunci = q.getResultList();
        
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        if ("".equals(vicinoA) && utenteLoggato != null) vicinoA = utenteLoggato.getIndirizzo().getDescrizione();
        return  utilities.AnnuncioConDistanza.ordina(annunci, vicinoA);
    }

    /**
     * Esegue una ricerca tra gli annunci coi parametri specificati, che sono tutti opzionali
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @param sito sito a cui si riferisce la ricerca
     * @param dataChiusura data massima di chiusura degli annunci cercati
     * @param dataOrdine data massima di ordine degli annunci cercati
     * @param vicinoA indirizzo in base a cui sono ordinati i risultati
     * @param chiusi true sse si vogliono includere anche gli annunci chiusi
     * @param minFeed feedback minimo che deve avere il creatore di un annuncio per restituirlo
     * @return la lista di annunci corrispondenti
     * @throws CampoErratoException se ci sono dei parametri di ricerca non nel formato corretto
     */
    @Override
    public List<AnnuncioConDistanza> cerca(UtenteRegistrato utenteLoggato, String sito, String dataChiusura, String dataOrdine, String vicinoA, boolean chiusi, int minFeed) throws CampoErratoException{
        
        String errori = controllaCampiRicerca(sito, dataChiusura, dataOrdine, vicinoA, null, null);
        if (!"".equals(errori)) throw new CampoErratoException(errori);
        
        QueryBuilder qb = new QueryBuilder("Annuncio", em);

        if (Regexp.hasContent(sito)) {
            qb.addWhereEqClause("sito.url", sito);
        }
        if (Regexp.hasContent(dataChiusura)) {
            qb.addWhereMoreEqClause("dataChiusura", stringToDate(dataChiusura));
        }
        if (Regexp.hasContent(dataOrdine)) {
            qb.addWhereMoreEqClause("dataOrdine", stringToDate(dataOrdine));
        }
        if (!chiusi) {
            qb.addWhereLessClause("dataChiusura", new GregorianCalendar());
        }
        qb.addWhereLessEqClause("creatoreAnnuncio.feedback.feedbackCreatore", minFeed);

        Query q = qb.getQueryObject();
        List annunci = q.getResultList();
        
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        if ("".equals(vicinoA) && utenteLoggato != null) vicinoA = utenteLoggato.getIndirizzo().getDescrizione();
        return  utilities.AnnuncioConDistanza.ordina(annunci, vicinoA);
    }

    /**
     * Esegue una ricerca tra gli annunci coi parametri specificati, che sono tutti opzionali
     * @param utenteLoggato l'utente a cui mostrare gli annunci, null se è un guest
     * @param sito sito a cui si riferisce la ricerca
     * @param dataChiusura data massima di chiusura degli annunci cercati
     * @param dataOrdine data massima di ordine degli annunci cercati
     * @param vicinoA indirizzo in base a cui sono ordinati i risultati
     * @param chiusi true sse si vogliono includere anche gli annunci chiusi
     * @param nomeCr nome del creatore degli annunci cercati
     * @param cognomeCr cognome del creatore degli annunci cercati
     * @return la lista di annunci corrispondenti
     * @throws CampoErratoException se ci sono dei parametri di ricerca non nel formato corretto
     */
    @Override
    public List<AnnuncioConDistanza> cerca(UtenteRegistrato utenteLoggato, String sito, String dataChiusura, String dataOrdine, String vicinoA, boolean chiusi, String nomeCr, String cognomeCr) throws CampoErratoException{
        
        String errori = controllaCampiRicerca(sito, dataChiusura, dataOrdine, vicinoA, nomeCr, cognomeCr);
        if (!"".equals(errori)) throw new CampoErratoException(errori);
        
        QueryBuilder qb = new QueryBuilder("Annuncio", em);

        if (Regexp.hasContent(sito)) {
            qb.addWhereEqClause("sito.url", sito);
        }
        if (Regexp.hasContent(dataChiusura)) {
            qb.addWhereMoreEqClause("dataChiusura", stringToDate(dataChiusura));
        }
        if (Regexp.hasContent(dataOrdine)) {
            qb.addWhereMoreEqClause("dataOrdine", stringToDate(dataOrdine));
        }
        if (!chiusi) {
            qb.addWhereLessClause("dataChiusura", new GregorianCalendar());
        }
        if (Regexp.hasContent(nomeCr)) {
            qb.addWhereEqClause("creatoreAnnuncio.nome", nomeCr);
        }
        if (Regexp.hasContent(cognomeCr)) {
            qb.addWhereEqClause("creatoreAnnuncio.cognome", cognomeCr);
        }

        qb.printQuery();

        Query q = qb.getQueryObject();
        List annunci = q.getResultList();
        
        if (utenteLoggato == null){
            annunci = forGuest(annunci);
        }
        
        if ("".equals(vicinoA) && utenteLoggato != null) vicinoA = utenteLoggato.getIndirizzo().getDescrizione();
        return  utilities.AnnuncioConDistanza.ordina(annunci, vicinoA);
    }

    /**
     * Persistenza
     * @param object
     */
    public void persist(Object object) {
        em.persist(object);
    }

    private String controllaCampiRicerca(String sito, String dataChiusura, String dataOrdine, String vicinoA, String nomeCr, String cognomeCr) {
        String errori = "";
        
        if (!Regexp.checkURL(sito, false)) errori += "<li>"+sito+" non &egrave; un sito valido</li>";
        if (!Regexp.checkDate(dataChiusura, false)) errori += "<li>"+dataChiusura+" non &egrave; una data valida</li>";
        if (!Regexp.checkDate(dataOrdine, false)) errori += "<li>"+dataOrdine+" non &egrave; una data valida</li>";
        if (Regexp.hasContent(vicinoA) && !controllerGMaps.isUniqueAddress(vicinoA)) errori += "<li>l'indirizzo \""+vicinoA+"\" non &egrave; stato riconosciuto</li>";
        if (!Regexp.checkAlphabetic(nomeCr, false)) errori += "<li>"+nomeCr+" non &egrave; un nome valido</li>";
        if (!Regexp.checkAlphabetic(nomeCr, false)) errori += "<li>"+cognomeCr+" non &egrave; un cognome valido</li>";

        return errori;
    }
    
    private GregorianCalendar stringToDate(String d){
        DateFormat dataP = new SimpleDateFormat("dd-MM-yyyy");
        GregorianCalendar res = new GregorianCalendar();
        try {
            res.setTime(dataP.parse(d));
        } catch (ParseException ex) {
            res = null;
        }
        return res;
    }

    // Nascondo i campi dell'annuncio che devono rimanere invisibili al guest
    // ovvero l'indirizzo (solo città) e il creatore.
    private List<Annuncio> forGuest(List<Annuncio> annunci) {
        List<Annuncio> res = new ArrayList<Annuncio>();
        for (Annuncio a: annunci){
            String citta = DistanceMatrix.getLocality(a.getIndirizzoRitiro().toString());
            res.add(annuncioForGuest(a, citta));
        }
        return res;
    }
    
    private Annuncio annuncioForGuest(Annuncio a, String citta){
        Annuncio res = new Annuncio();
        res.setAdesioni(a.getAdesioni());
        res.setCostoSpedizione(a.getCostoSpedizione());
        res.setCreatoreAnnuncio(null);
        res.setDataChiusura(a.getDataChiusura());
        res.setDataOrdine(a.getDataOrdine());
        IndirizzoAbitazione i = new IndirizzoAbitazione();
        i.setDescrizione(citta);
        res.setIndirizzoRitiro(i);
        res.setSito(a.getSito());
        res.setSpesaCreatore(a.getSpesaCreatore());
        res.setSpesaMinSpedGratis(a.getSpesaMinSpedGratis());
        res.setSupportaSpedGratis(a.isSupportaSpedGratis());
        return res;
    }

}
